# -*- coding: utf-8 -*-
import time
import socket
import threading
import queue
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
import psutil
import sys
import binascii



class NetAssistSocket(object):
    def __init__(self):
        self.status = False  # 状态 True 为连接,False 为断开
        self.exitstatus = True
        self._socket = None
        self.server_thread_instance = None
        self.client_thread_instance = None
        self.message_queue = queue.Queue()
        self.send_queue = queue.Queue()
        self.sendhex=False
        self.recvhex=False

    @staticmethod
    def get_all_ip_addresses():
        """
        获取所有网络接口的IPv4地址。

        本方法遍历所有网络接口，并提取其IPv4地址。对于每个网络接口，如果其地址族为AF_INET（IPv4），则收集该地址。

        Returns:
            dict: 一个字典，键为网络接口名称，值为该接口的IPv4地址。
        """
        # 初始化一个空字典，用于存储网络接口及其IPv4地址
        addresses = {}
        # 遍历所有网络接口及其地址信息
        for interface, addrs in psutil.net_if_addrs().items():
            # 遍历当前接口的所有地址
            for addr in addrs:
                # 检查地址族，如果为AF_INET（IPv4）
                if addr.family == socket.AF_INET:
                    # 获取IPv4地址并存储
                    ip = addr.address
                    addresses[interface] = ip
        # 返回包含所有网络接口IPv4地址的字典
        return addresses
    def parse_hex_data(self, received_data):
        hex_str = binascii.hexlify(received_data).decode('utf - 8')
        spaced_hex_str = " ".join([hex_str[i:i + 2] for i in range(0, len(hex_str), 2)])
        return spaced_hex_str
    def tcp_server_thread(self, host, port):
        """
        启动服务器线程，负责监听和处理来自客户端的连接请求。

        参数:
        - host: 服务器监听的主机地址。
        - port: 服务器监听的端口号。

        该函数创建一个TCP套接字，绑定到指定的主机地址和端口，开始监听并接受来自客户端的连接。
        一旦接受到连接，即创建一个新的线程来处理该连接，确保服务器可以同时处理多个客户端请求。
        """
        # 创建TCP套接字
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 绑定套接字到指定的主机和端口
        self._socket.bind((host, port))
        # 开始监听，最多同时接受5个未处理的连接请求
        self._socket.listen(5)

        print(f"服务器正在监听端口 {port}...")
        # 主循环，持续接受客户端的连接请求，直到服务器关闭
        while self.status:
            try:
                # 接受一个客户端连接
                client_socket, addr = self._socket.accept()
                # 如果服务器已经停止，则关闭客户端套接字并退出循环
                if not self.status:
                    client_socket.close()
                    break
                print(f"连接来自：{addr}")
                # 接受连接后，处理与客户端的数据交互
                while True:
                    try:
                        # 接收数据，每次最多接收1024字节
                        data = client_socket.recv(1024)
                        # 如果没有接收到数据，说明连接已关闭
                        if not data:
                            print(f"没有数据")
                            break

                        print(self.recvhex)
                        if self.recvhex:
                            data = self.parse_hex_data(data)
                        print(f"收到数据：{data.decode()}")
                        # 记录接收到数据的时间
                        local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                        # 构造消息字符串，包括时间戳和数据内容
                        message = f"[{local_time}]# recv TCP from {addr} <<<<\n{data.decode()}"
                        # 将消息放入消息队列，以便其他部分处理或记录
                        self.message_queue.put(message)

                        # 先回复信息
                        '''
                        print("to reply")
                        while self.status:
                            # 检查发送队列是否有数据
                            print("in reply")
                            if not self.send_queue.empty():
                                # 从队列中获取数据
                                data = self.send_queue.get()
                                # 发送数据到服务器
                                client_socket.sendall(data.encode())
                                print(f"已发送数据：{data}")
                            else:
                                # 队列为空，休眠等待
                                print("无回复数据")
                                break
                        '''
                    except:  # noqa
                        # 如果发生异常，退出循环
                        break
                # 关闭客户端套接字
                client_socket.close()
            except:  # noqa
                # 如果发生超时，跳过当前循环
                pass

        # 如果套接字已创建，关闭服务器套接字
        if self._socket:
            self._socket.close()
        print("服务器线程已关闭")

    def tcp_start_server(self, host, port):
        self.status = True
        if not self._socket:
            self.server_thread_instance = threading.Thread(target=self.tcp_server_thread, args=(host, port))
            self.server_thread_instance.start()
            print("start tcp server thread")
        else:
            print("服务器已经在运行中。")

    def tcp_stop_server(self, host, port):
        # 设置状态标志，以便停止服务器循环
        self.status = False
        print("stop tcp server start "+host+","+str(port))
        # 检查是否存在已初始化的套接字对象
        if self._socket:
            try:
                # 通过创建一个临时套接字尝试连接服务器，触发服务器的 accept 方法中的退出条件
                temp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                temp_socket.connect((host, port))
                # 关闭临时套接字
                temp_socket.close()
            except Exception as e:  # noqa
                # 如果连接失败，忽略异常
                print(f"err:{e}")
                pass

            # 关闭服务器套接字
            self._socket.close()
            # 重置套接字对象引用
            self._socket = None

            # 等待服务器线程结束
            self.server_thread_instance.join()

        # 输出服务器停止信息
        print("服务器已停止")

    def client_thread(self, host, port):
        """
        客户端线程函数，用于与服务器建立连接并发送数据。

        参数:
            host (str): 服务器主机地址。
            port (int): 服务器端口号。

        返回:
            无
        """
        try:
            # 创建TCP套接字
            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 尝试连接到服务器
            self._socket.connect((host, port))
            print(f"已连接到服务器：{host}:{port}")

            # 主循环，运行客户端操作
            while self.status:
                # 检查发送队列是否有数据
                if not self.send_queue.empty():
                    # 从队列中获取数据
                    data = self.send_queue.get()
                    #add hex 
                    print(self.sendhex)
                    if self.sendhex:
                        hex_data_str_list = data.split()
                        hex_data_bytes_list = []
                        for hex_str in hex_data_str_list:
                            hex_data_bytes_list.append(bytes.fromhex(hex_str))
                        # 发送数据
                        #print(hex_data_bytes_list)
                        #for hex_data_bytes in hex_data_bytes_list:
                        #    self._socket.send(hex_data_bytes)
                        self._socket.sendall(b"".join(hex_data_bytes_list))
                        print(f"已发送HEX数据：{hex_data_bytes_list}")
                    else:
                        # 发送数据到服务器
                        self._socket.sendall(data.encode())
                        print(f"已发送数据：{data}")
                else:
                    # 队列为空，休眠等待
                    time.sleep(0.5)
                '''
                try:
                    while True:
                        # 接收数据，每次最多接收1024字节
                        data = self._socket.recv(1024)
                        # 如果没有接收到数据，说明连接已关闭
                        if not data:
                            print(f"没有数据")
                            break
                        print(f"收到数据：{data.decode()}")
                        # 记录接收到数据的时间
                        local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                        # 构造消息字符串，包括时间戳和数据内容
                        message = f"[{local_time}]# recv TCP from {addr} <<<<\n{data.decode()}"
                        # 将消息放入消息队列，以便其他部分处理或记录
                        self.message_queue.put(message)
                except:
                    break
                '''
        except ConnectionRefusedError:
            # 连接被拒绝，打印错误信息
            print(f"无法连接到服务器：{host}:{port}")

        # 关闭套接字
        if self._socket:
            self._socket.close()

        # 打印断开连接信息
        print("客户端已断开连接")

    def start_client(self, host, port):
        self.status = True
        if not self._socket:
            self.client_thread_instance = threading.Thread(target=self.client_thread, args=(host, port))
            self.client_thread_instance.start()
            print("start client thread")
        else:
            print("服务器已经在运行中。")

    def stop_client(self):
        self.status = False
        if self._socket:
            self._socket.close()
            self._socket = None
            self.client_thread_instance.join()
        print("客户端已停止")

    @staticmethod
    def client_send_udp_message(host, port, data):
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        client_socket.sendto(data.encode(), (host, port))
        client_socket.close()

    def udp_server_thread(self, host, port):
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._socket.bind((host, port))
        print(f"UDP 服务器正在监听端口 {port}...")
        while self.status:
            try:
                data, addr = self._socket.recvfrom(1024)
                if not self.status:
                    break
                print(f"从 {addr} 收到数据：{data.decode()}")
                # 记录接收到数据的时间
                local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
                # 构造消息字符串，包括时间戳和数据内容
                message = f"[{local_time}]# recv UDP from {addr} <<<<\n{data.decode()}"
                # 将消息放入消息队列，以便其他部分处理或记录
                self.message_queue.put(message)
            except Exception as e:
                print(f"接收数据时发生错误: {e}")
        if self._socket:
            self._socket.close()
        print("UDP 服务器已关闭")

    def udp_start_server(self, host, port):
        self.status = True
        if not self._socket:
            self.server_thread_instance = threading.Thread(target=self.udp_server_thread, args=(host, port))
            self.server_thread_instance.start()
            print("start udpserver thread")
        else:
            print("服务器已经在运行中。")

    def udp_stop_server(self):
        self.status = False
        if self._socket:
            self._socket.close()
            self._socket = None
            self.server_thread_instance.join()
        print("服务器已停止")

class NetAssistUI(object):
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("loongtcp")
        self.socket = NetAssistSocket()
        self.log_thread = None
        self.net = dict(labels=dict(), type=dict())
        self.receive = dict(labels=dict(ip=dict(text='本地主机地址'), port=dict(text='本地主机端口')))
        self.receive['encoding'] = tk.StringVar(value='ASCII')
        self.receive['log'] = tk.IntVar(value=0)
        self.receive['auto_wrap'] = tk.IntVar(value=0)
        self.receive['hidden'] = tk.IntVar(value=0)
        self.send = dict()
        self.labels = {"TCP Server": dict(ip="本地主机地址", port="本地主机端口"), "TCP Client": dict(ip="远程主机地址", port="远程主机端口"),
                       "UDP": dict(ip="主机地址", port="主机端口")}
        self.style = ttk.Style()

        self.init_style()

        # 创建 LabelFrames 并进行网格布局
        self.frame_network = ttk.LabelFrame(self.root, text='网络设置')
        self.frame_receive = ttk.LabelFrame(self.root, text='接收设置')
        self.frame_send = ttk.LabelFrame(self.root, text='发送设置')
        self.frame_receive_data = ttk.LabelFrame(self.root, text='数据日志')
        self.frame_send_data = ttk.LabelFrame(self.root)

        # 调整 LabelFrames 的布局
        self.frame_network.grid(row=0, column=0, sticky='nsew', padx=5, pady=5)
        self.frame_receive.grid(row=1, column=0, sticky='nsew', padx=5, pady=5)
        self.frame_send.grid(row=2, column=0, sticky='nsew', padx=5, pady=5)
        self.frame_receive_data.grid(row=0, column=1, rowspan=2, sticky='nsew')
        self.frame_send_data.grid(row=2, column=1, sticky='nsew', padx=5, pady=5)

        # 设置网格权重
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_rowconfigure(1, weight=1)
        self.root.grid_rowconfigure(2, weight=1)
        self.root.grid_rowconfigure(3, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_columnconfigure(1, weight=1)

        # 初始化各部分设置
        self.network_settings()
        self.receive_settings()
        self.send_settings()
        self.receive_data()
        self.send_data()

        self.init_data()
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.text_log.insert(tk.END, f'loong netassist by wjh\n')
    
    def on_closing(self):
        print("窗口关闭，程序退出")
        # if self._socket:
        try:
            print(self.net['ip'].get()+"p:"+self.net['port'].get())
            self.socket.tcp_stop_server(self.net['ip'].get(),int(self.net['port'].get()))
            self.socket.stop_client()
            self.socket.udp_stop_server()
        except:
            pass
        #     self.socket.log_thread.close()
        #     self.socket.log_thread=None
        #     self.socket.log_thread.join()
        # self.socket.quit()
        # self.root.destroy()
        # self.root.quit()
        self.socket.exitstatus = False
        self.log_thread.join(1)
        sys.exit()
        # exit()
        # os._exit()
        #quitmain()

    def init_data(self):
        addresses = self.socket.get_all_ip_addresses()
        self.net['ip']['values'] = [i for i in addresses.values() if i != '127.0.0.1']

    def select_protocol(self, event):
        value = event.widget.get()
        self.net['labels']['ip'].configure(text=self.labels[value]['ip'])
        self.net['labels']['port'].configure(text=self.labels[value]['port'])

    def init_style(self):
        self.style.configure('Send.TButton', borderwidth=0, relief='flat', width=5, height=5)

    def network_settings(self):
        ttk.Label(self.frame_network, text="协议类型").grid(row=0, column=0, sticky='e')
        self.net['type'] = ttk.Combobox(self.frame_network, values=("TCP Server", "TCP Client", "UDP"), width=10)
        self.net['type'].grid(row=0, column=1, sticky='w')
        self.net['type'].bind("<<ComboboxSelected>>", self.select_protocol)

        self.net['labels']['ip'] = ttk.Label(self.frame_network, text="本地主机地址")
        self.net['labels']['ip'].grid(row=1, column=0, sticky='e')
        self.net['ip'] = ttk.Combobox(self.frame_network, width=10)
        self.net['ip'].grid(row=1, column=1, sticky='w')
        self.net['labels']['port'] = ttk.Label(self.frame_network, text="本地主机端口")
        self.net['labels']['port'].grid(row=2, column=0, sticky='e')
        # 创建验证命令
        vcmd = (self.frame_send.register(self.validate_input), '%P')
        self.net['port'] = ttk.Entry(self.frame_network, validate='key', validatecommand=vcmd, textvariable=tk.IntVar(value=8080), width=11)
        self.net['port'].grid(row=2, column=1, sticky='w')
        self.net['button'] = ttk.Button(self.frame_network, text="连接", command=self.connection)
        self.net['button'].grid(row=3, column=0, columnspan=2, pady=5)

    def receive_settings(self):
        self.receive['encoding'] = tk.StringVar(value='ASCII')
        self.receive['log'] = tk.IntVar(value=0)
        self.receive['auto_wrap'] = tk.IntVar(value=0)
        self.receive['hidden'] = tk.IntVar(value=0)
        ttk.Radiobutton(self.frame_receive, text="ASCII", variable=self.receive['encoding'], value="ASCII",command=self.radio_button_clicked).grid(row=0, column=0, sticky='w')
        ttk.Radiobutton(self.frame_receive, text="HEX", variable=self.receive['encoding'], value="HEX",command=self.radio_button_clicked).grid(row=0, column=1, sticky='w')
        ttk.Checkbutton(self.frame_receive, text="按日志模式显示", variable=self.receive['log']).grid(row=1, column=0, columnspan=2, sticky='w')
        ttk.Checkbutton(self.frame_receive, text="接收区自动换行", variable=self.receive['auto_wrap']).grid(row=2, column=0, columnspan=2, sticky='w')
        ttk.Checkbutton(self.frame_receive, text="接收数据不显示", variable=self.receive['hidden']).grid(row=3, column=0, columnspan=2, sticky='w')

    def radio_button_clicked(self):
        asciiorhex = self.receive['encoding'].get()
        if asciiorhex=='HEX':
            self.socket.recvhex = True
        else:
            self.socket.recvhex = False
    def sencoding_clicked(self):
        asciiorhex = self.send['encoding'].get()
        if asciiorhex=='HEX':
            self.socket.sendhex = True
        else:
            self.socket.sendhex = False
    def send_settings(self):
        self.send['encoding'] = tk.StringVar(value='ASCII')
        self.send['auto_parse'] = tk.IntVar(value=0)
        self.send['auto_bits'] = tk.IntVar(value=0)
        self.send['cycle_period'] = tk.IntVar(value=0)
        self.send['cycle_interval'] = tk.IntVar(value=1000)

        # 创建验证命令
        vcmd = (self.frame_send.register(self.validate_input), '%P')

        ttk.Radiobutton(self.frame_send, text="ASCII", variable=self.send['encoding'], value="ASCII",command=self.sencoding_clicked).grid(row=0, column=0, sticky='w')
        ttk.Radiobutton(self.frame_send, text="HEX", variable=self.send['encoding'], value="HEX",command=self.sencoding_clicked).grid(row=0, column=1, sticky='w')
        ttk.Checkbutton(self.frame_send, text="自动解析转义符", variable=self.send['auto_parse']).grid(row=1, column=0, columnspan=2, sticky='w')
        ttk.Checkbutton(self.frame_send, text="自动发送附加位", variable=self.send['auto_bits']).grid(row=2, column=0, columnspan=2, sticky='w')
        ttk.Checkbutton(self.frame_send, text="循环周期", variable=self.send['cycle_period']).grid(row=3, column=0, sticky='w')
        ttk.Entry(self.frame_send, validate='key', validatecommand=vcmd, width=4, font=("Arial", 11)).grid(row=3, column=1, sticky='w')
        ttk.Label(self.frame_send, text="ms").grid(row=3, column=2, sticky='w')

    def receive_data(self):
        self.text_log = tk.Text(self.frame_receive_data, width=65, height=20)
        self.text_log.pack(fill='both', expand=True)
        self.log_thread = threading.Thread(target=self.update_log)
        self.log_thread.start()
        print("start log thread")

    def send_data(self):
        # 创建新的 Frame 作为 labelwidget
        label_frame = ttk.Frame(self.frame_send_data)
        label_frame.pack(fill=tk.X)
        # 在 labelwidget 中添加标题和按钮
        ttk.Label(label_frame, text='数据发送').pack(side=tk.LEFT, padx=5, pady=5)
        tk.Button(label_frame, text="↑清除", compound='left', command=lambda: self.text_log.delete('1.0', tk.END)).pack(side=tk.RIGHT, padx=5, pady=5)
        tk.Button(label_frame, text="↓清除", compound='left', command=lambda: self.send['data'].delete('1.0', tk.END)).pack(side=tk.RIGHT, padx=5,
                                                                                                                            pady=5)
        frame_send_data = ttk.Frame(self.frame_send_data)
        frame_send_data.pack(fill=tk.BOTH, expand=True)
        self.send['data'] = tk.Text(frame_send_data, width=65, height=3)
        self.send['data'].pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        # 发送按钮
        tk.Button(frame_send_data, text="发送", command=self.client_send_to_server, width=5, height=5).pack(side=tk.LEFT)

    def validate_input(self, new_value):
        """
        验证输入是否为数字。

        该方法用于检查传入的 new_value 是否是数字或者为空字符串。如果满足条件，
        则将 new_value 赋值给类的 send 属性中的 'new_value' 字段，并返回 True。
        如果 new_value 不是数字且不为空，则弹出一个警告对话框，提示用户只能输入数字，
        并返回 False。

        参数:
        new_value (str): 需要验证的输入值。

        返回:
        bool: 如果输入值是数字或为空字符串，则返回 True；否则返回 False。
        """
        # 检查 new_value 是否是数字或为空字符串
        if new_value.isdigit() or new_value == "":
            # 如果是数字或为空字符串，更新 send 属性并返回 True
            self.send['new_value'] = new_value
            return True
        else:
            # 如果不是数字且不为空，显示警告对话框并返回 False
            messagebox.showwarning("错误", "只能输入数字！")
            return False

    def connection(self):
        ip = self.net['ip'].get()
        port = self.net['port'].get()
        print("stats:"+self.net['type'].get())
        # 协议
        protocol = socket.SOCK_STREAM
        if 'UDP' in self.net['type'].get():
            protocol = socket.SOCK_DGRAM
        if "TCP Server" == self.net['type'].get():
            if self.socket.status:
                self.socket.tcp_stop_server(ip, int(port))
            else:
                self.socket.tcp_start_server(ip, int(port))
        elif "TCP Client" == self.net['type'].get():
            if self.socket.status:
                self.socket.stop_client()
            else:
                self.socket.start_client(ip, int(port))
        elif "UDP" == self.net['type'].get():
            if self.socket.status:
                self.socket.udp_stop_server()
            else:
                self.socket.udp_start_server(ip, int(port))

        if self.socket.status:
            self.net['button'].config(text="关闭")
        else:
            self.net['button'].config(text="连接")

    def update_log(self):
        while self.socket.exitstatus:
            if not self.socket.message_queue.empty():
                message = self.socket.message_queue.get()
                self.text_log.insert(tk.END, f'{message}\n')
                # 自动滚动到末尾
                self.text_log.see(tk.END)
            else:
                time.sleep(0.5)

    def client_send_to_server(self):
        data = self.send['data'].get("1.0", tk.END).strip()
        if self.net['type'].get() == "UDP":
            self.socket.client_send_udp_message(self.net['ip'].get(), int(self.net['port'].get()), data)
        else:
            #add hex 在事件中做了
            '''
            asciiorhex = self.send['encoding'].get()
            if asciiorhex=='HEX':
                self.socket.sendhex = True
            else:
                self.socket.sendhex = False
            '''
            self.socket.send_queue.put(data)
        local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        self.text_log.insert(tk.END, f"[{local_time}]# SEND {len(data)}>>>>\n{data}\n")

    def run(self):
        self.root.mainloop()

def quitmain():
    exit()

if __name__ == '__main__':
    NetAssistUI().run()
